﻿namespace Xml
{
    #region usings

    using System;
    using System.IO;
    using System.Linq;
    using System.Xml.Linq;
    using System.Xml.Serialization;

    #endregion

    internal sealed class DataSerializer<TData>
        where TData : new()
    {
        #region Constants and Fields

        private readonly Type type;

        private XmlSerializer serializer;

        #endregion

        #region Constructors and Destructors

        public DataSerializer()
        {
            type = typeof(TData);
            serializer = new XmlSerializer(type);
        }

        #endregion

        #region Public Methods

        public static TData ReadFile(string file)
        {
            var serializer = new DataSerializer<TData>();
            try
            {
                string xml;
                using (var reader = new StreamReader(file))
                {
                    xml = reader.ReadToEnd();
                    reader.Close();
                }

                return serializer.Deserialize(xml);
            }
            catch (Exception ex)
            {
                throw new IOException("Can't read file '" + file + "'", ex);
            }
        }

        public static bool WriteFile(string file, TData config, bool preserveWhitespace)
        {
            var serializer = new DataSerializer<TData>();
            try
            {
                string xml = serializer.Serialize(config, preserveWhitespace).ToString();
                using (var writer = new StreamWriter(file, false))
                {
                    writer.Write(xml);
                    writer.Flush();
                    writer.Close();
                }

                return true;
            }
            catch (Exception ex)
            {
                throw new IOException("Can't write to file '" + file + "'", ex);
            }
        }

        public TData Deserialize(string xml)
        {
            TextReader reader = new StringReader(xml);
            return Deserialize(reader);
        }

        public TData Deserialize(XDocument doc)
        {
            TextReader reader = new StringReader(doc.ToString());
            return Deserialize(reader);
        }

        public TData Deserialize(TextReader reader)
        {
            // TODO: try catch
            var data = (TData)serializer.Deserialize(reader);
            reader.Close();
            return data;
        }

        public XDocument Serialize(TData data, bool preserveWhitespace)
        {
            // TODO: try catch
            string xml = StringSerialize(data);
            var document = XDocument.Parse(xml, preserveWhitespace ? LoadOptions.PreserveWhitespace : LoadOptions.None);
            document = Clean(document);
            return document;
        }

        #endregion

        #region Methods

        private static XDocument Clean(XDocument doc)
        {
            var attributes =
                doc.Descendants().Where(x => x.Attributes().Count() > 0).SelectMany(element => element.Attributes());
            var attributesToRemove =
                attributes.Where(
                    x => x.IsNamespaceDeclaration && (x.Name.LocalName == "xsi" || x.Name.LocalName == "xsd"));
            attributesToRemove.ToList().ForEach(x => x.Remove());
            return doc;
        }

        private string StringSerialize(TData data)
        {
            TextWriter writer = WriterSerialize(data);
            string xml = writer.ToString();
            writer.Close();
            return xml;
        }

        private TextWriter WriterSerialize(TData data)
        {
            // TODO: try catch
            TextWriter writer = new StringWriter();
            serializer = new XmlSerializer(type);
            serializer.Serialize(writer, data);
            writer.Flush();
            return writer;
        }

        #endregion
    }
}